package com.qire.manhua.view.comicReader;

import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.BaseAdapter;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.PointF;
import android.database.DataSetObserver;

import com.qire.common.function.Consumer;
import com.qire.manhua.R;
import com.qire.manhua.view.comicReader.scroller.ComicReadFlingAnimator;
import com.qire.manhua.view.comicReader.scroller.SummerOverScroller;

import androidx.annotation.Nullable;

/**
 * 无限循环漫画阅读器 <br />
 * 实现了一个简单的ListView类似功能，采取3个漫画阅读器构成 上下预加载章节和正在阅读章节的结构，配合一个移动的阅读器来复用无限循环。
 * <pre>
 *               Before           After
 *             +-------+        +-------+
 *             |   A   |        |   B   |
 *             +-------+   =>   +-------+
 *   |   M   | |   B   |        |   C   |  |   A   |
 *             +-------+        +-------+
 *             |   C   |        |   M   |
 *             +-------+        +-------+
 * </pre>
 */
public class LoopComicReaderView extends ViewGroup {

    /**
     * 循环回收站
     */
    private final LooperBin looperBin = new LooperBin();
    /**
     * 数据适配器
     */
    private BaseAdapter mAdapter;
    private AdapterDataSetObserver mDataSetObserver;
    private ComicReaderListener mComicReaderListener;
    private int mItemCount;

    private SummerFlingRunnable mFlingRunnable;
    private int mMinimumVelocity;
    private int mMaximumVelocity;
    private int mCumulativeMaximumVelocity;
    private int mOverScrollDistance;
    private int mOverFlingDistance;
    private int mTouchSlopSquare;
    private float mVelocityScale = 1.0f;

    private static final int INVALID_POINTER = -1;

    private int mActivePointerId = INVALID_POINTER;

    public static final int TOUCH_MODE_REST = -1;
    public static final int TOUCH_MODE_DOWN = 0;
    public static final int TOUCH_MODE_SCROLL = 3;
    public static final int TOUCH_MODE_FLING = 4;
    public static final int TOUCH_MODE_OVER_SCROLL = 5;
    public static final int TOUCH_MODE_OVER_FLING = 6;

    private int mTouchMode = TOUCH_MODE_REST;
    private VelocityTracker mVelocityTracker;
    private int mLastY;

    private boolean initLayout = true;

    public LoopComicReaderView(Context context) {
        this(context, null);
    }

    public LoopComicReaderView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoopComicReaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 初始话控件，配置滑动相关，触碰脏域范围等
     */
    private void init() {
        setClickable(true);
        setFocusableInTouchMode(true);
        setWillNotDraw(false);
        setAlwaysDrawnWithCacheEnabled(false);

        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity() / 3;             //限制滑动速度为系统默认速度的1/3
        mMaximumVelocity = Math.max(mMaximumVelocity, 10000);                             //在不同机型默认速度取值不一，保证同步至少取到10000
        mCumulativeMaximumVelocity = (mMaximumVelocity * 10) / 4;                           //允许连续滑动时累加滑动速度最大上限是单次跟踪滑动最大速度的2.5倍
        mOverScrollDistance = configuration.getScaledOverscrollDistance();//100;//
        mOverFlingDistance = configuration.getScaledOverflingDistance();

        int touchSlop = configuration.getScaledTouchSlop();
        mTouchSlopSquare = touchSlop * touchSlop;
    }

    //region    #继承View生命周期函数 begin
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
//        super.measureChild(child, parentWidthMeasureSpec, parentHeightMeasureSpec);
        LayoutParams p = child.getLayoutParams();
        final int childWidthSpec = ViewGroup.getChildMeasureSpec(parentWidthMeasureSpec, 0, p.width);
        final int lpHeight = p.height;
        final int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.UNSPECIFIED);
        }
        child.measure(childWidthSpec, childHeightSpec);

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        looperBin.onLayout(changed, l, t, r, b);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //todo 虚拟导航高度获取问题
        final int height = h;
        looperBin.forEach(viewWrapper -> {
            ComicReaderView comicReaderView = viewWrapper.findComicReaderView();
            if(comicReaderView.getLayoutParams().height < height) {
                comicReaderView.getLayoutParams().height = height;
                comicReaderView.requestLayout();
            }
        });
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
            mDataSetObserver = null;
        }
    }

    //endregion #继承View生命周期函数 end

    //region    #对外开放函数 begin

    /**
     * 设置无限阅读器事件监听器，用于捕获预加载，阅读，解锁，边缘等事件。
     * @param comicReaderListener 监听器
     */
    public void setComicReaderListener(ComicReaderListener comicReaderListener) {
        mComicReaderListener = comicReaderListener;
    }

    /**
     * @return 返回适配器，如果未设置为NULL
     */
    public BaseAdapter getAdapter() {
        return mAdapter;
    }

    /**
     * 跳转到给定位置项目并加载内容
     * @param position 阅读项目位置索引
     */
    public void gotoPosition(int position) {
        position = Math.max(position, 0);
        position = Math.min(position, (mItemCount - 1));

        int currentPosition = looperBin.currentView().position();

        if(currentPosition == position) {
            return;
        }

        looperBin.movePosition(position);

        fillData();

        if(mComicReaderListener != null) {
//            mComicReaderListener.onPreRead(previousIndex, mItemCount);
//            mComicReaderListener.onPreRead(nextIndex, mItemCount);
            mComicReaderListener.onPreRead(position - 1, mItemCount);
            mComicReaderListener.onPreRead(position + 1, mItemCount);
            mComicReaderListener.onReading(position, mItemCount);
        }
    }

    /**
     * 翻页
     * @param down true 为下翻 false则上翻
     */
    public void pageTurning(boolean down) {
        if (down) {
            looperBin.fillDown(getTop());
        } else {
            looperBin.fillUp(getBottom());
        }
        looperBin.correctionViewListDock();
    }

    private boolean bulletOnOff = true;
    public void bulletSwitch(boolean bulletOnOff) {
        this.bulletOnOff = bulletOnOff;
        ComicReaderView comicReaderView = pickVisibleComicReaderView(true);
        comicReaderView.setIsOpenBulletScreen(bulletOnOff);
    }

    public boolean bulletOnOff() {
        return bulletOnOff;
    }

    /**
     * 继续播放弹幕
     */
    public void goPlayBulletScreen() {
        looperBin.forEach(viewWrapper -> viewWrapper.findComicReaderView().goPlayBulletScreen());
    }

    /**
     * 暂停播放弹幕
     */
    public void pauseBulletScreen() {
        looperBin.forEach(viewWrapper -> viewWrapper.findComicReaderView().pauseBulletScreen());
    }

    /**
     * 获得正在阅读的漫画视图*/
    public ComicReaderView getReadingComicReaderView() {
        return looperBin.currentView().findComicReaderView();
    }

    ComicReadFlingAnimator comicReadFlingAnimator = new ComicReadFlingAnimator(this);

    /**
     * 滑动一个偏移距离
     * @param offset 偏移距离
     */
    public void smoothScrollByOffset(int offset) {
//        if (mFlingRunnable == null) {
//            mFlingRunnable = new SummerFlingRunnable();
//        }
//        mFlingRunnable.endFling();
//        mFlingRunnable.start(offset);
        if(!comicReadFlingAnimator.isRunning())
            comicReadFlingAnimator.setDistanceY(offset).start();
    }

    /**
     * 移动一个偏移距离
     * @param incrementalDeltaY Y轴上的偏移增量
     */
    public void motionScrollY(int incrementalDeltaY) {
        trackMotionScroll(incrementalDeltaY);
    }

    /**
     * @return 当前触摸模式：闲置，滑动，抛帅中
     */
    public int getTouchMode() {
        return mTouchMode;
    }

    public void stopFlingAndDock() {
        if(mFlingRunnable != null) {
            mFlingRunnable.endFling();
        }
    }

    //endregion #对外开放函数 end

    //region    #滑动 begin

    private MotionEvent lastDownEvent;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 解决子控件抢占事件后导致滑动事件冲突无法响应
        // 拦截滑动事件
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (lastDownEvent != null) {
                    lastDownEvent.recycle();
                    lastDownEvent = null;
                }
                lastDownEvent = MotionEvent.obtain(ev);
                downPoint = new PointF((int) lastDownEvent.getX(), (int) lastDownEvent.getY());
                break;
            case MotionEvent.ACTION_MOVE:
                if(checkSlopSquare(x, y)) {
                    if (lastDownEvent != null) {
                        //拦截后补充按下事件
                        onTouchEvent(lastDownEvent);
                        lastDownEvent.recycle();
                        lastDownEvent = null;
                    }
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (!isEnabled()) {
            return isClickable() || isLongClickable();
        }

        initVelocityTrackerIfNotExists();

        final MotionEvent vtev = MotionEvent.obtain(ev);
        vtev.offsetLocation(0, 0);

        final int actionMasked = ev.getActionMasked();

        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN: {
                pauseBulletScreen();
                onTouchDown(ev);
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                onTouchMove(ev);
                break;
            }

            case MotionEvent.ACTION_UP: {
                goPlayBulletScreen();
                onTouchUp(ev);
                break;
            }

            case MotionEvent.ACTION_CANCEL: {
                goPlayBulletScreen();
                onTouchCancel();
                break;
            }

            case MotionEvent.ACTION_POINTER_DOWN: {
                final int index = ev.getActionIndex();
                final int id = ev.getPointerId(index);
                final int y = (int) ev.getY(index);
                mActivePointerId = id;
                mLastY = y;
                break;
            }

        }

        if (mVelocityTracker != null) {
            mVelocityTracker.addMovement(vtev);
        }
        vtev.recycle();
        return true;
    }

    /**
     * 初始化速度跟踪器(如果不存在)
     */
    private void initVelocityTrackerIfNotExists() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
    }

    /**
     * 回收速度跟踪
     */
    private void recycleVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    /**
     * 手指按下时记录按下的点，该变量使用在脏区计算中，判定当前手指触碰行为，是否是误点或是滑动，固不应该在移动中被修改。
     * 如有需要记录移动点，可另行申明。
     */
    private PointF downPoint;

    /**
     * 当手指按下时
     * @param ev
     */
    private void onTouchDown(MotionEvent ev) {

        mActivePointerId = ev.getPointerId(0);
        downPoint = new PointF(ev.getX(), ev.getY());
        mLastY = (int) ev.getY();

        if (mTouchMode == TOUCH_MODE_OVER_FLING) {
            mFlingRunnable.endFling();
            mTouchMode = TOUCH_MODE_OVER_SCROLL;
        } else {
            if (mTouchMode == TOUCH_MODE_FLING) {
                mTouchMode = TOUCH_MODE_SCROLL;
                mFlingRunnable.flywheelTouch();
            }
        }
    }

    /**
     * 滑动脏区判定，手指按住不动时，上下抖动距离不大的范围以像素为单位的判定一定会有跟随手指的扭动。
     * 体验上来说并不良好。*/
    private boolean checkSlopSquare(int x, int y) {
        float distanceX = downPoint.x - x;
        float distanceY = downPoint.y - y;
        float adbDistanceX = Math.abs(distanceX);
        float adbDistanceY = Math.abs(distanceY);
        return (adbDistanceX * adbDistanceX) + (adbDistanceY * adbDistanceY) >= mTouchSlopSquare;
    }

    /**
     * 如果构成滚动条件就滚动
     * @param x 移动的x坐标
     * @param y 移动的y坐标
     */
    private void scrollIfNeeded(int x, int y) {
        int incrementalDeltaY = y - mLastY;
        boolean isNeeded = checkSlopSquare(x, y);
        if (incrementalDeltaY != 0 && isNeeded) {
            trackMotionScroll(incrementalDeltaY);
        }
    }

    /**
     * 当手指移动时
     * @param ev
     */
    private void onTouchMove(MotionEvent ev) {
        int pointerIndex = ev.findPointerIndex(mActivePointerId);
        if (pointerIndex == -1) {
            pointerIndex = 0;
            mActivePointerId = ev.getPointerId(pointerIndex);
        }

        final int x = (int) ev.getX(pointerIndex);
        final int y = (int) ev.getY(pointerIndex);
        scrollIfNeeded(x, y);

        mTouchMode = TOUCH_MODE_SCROLL;
        mLastY = y;
    }

    /**
     * 当手指抬起时
     * @param ev
     */
    private void onTouchUp(MotionEvent ev) {

        switch (mTouchMode) {
            case TOUCH_MODE_DOWN:
                mTouchMode = TOUCH_MODE_REST;
                break;
            case TOUCH_MODE_SCROLL:
                final int childCount = getChildCount();
                final int dataIndex = looperBin.previousView().position();
                if (childCount > 0) {
                    final int firstChildTop = looperBin.nextView().top();//getChildAt(0).getTop();
                    final int lastChildBottom = looperBin.nextView().bottom();//getChildAt(childCount - 1).getBottom();
                    final int contentTop = 0;
                    final int contentBottom = getHeight();
                    if (dataIndex == 0 && firstChildTop >= contentTop && dataIndex + childCount < mItemCount && lastChildBottom <= getHeight() - contentBottom) {
                        mTouchMode = TOUCH_MODE_REST;
                    } else {
                        final VelocityTracker velocityTracker = mVelocityTracker;
                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);

                        final int initialVelocity = (int) (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
                        // Fling if we have enough velocity and we aren't at a boundary.
                        // Since we can potentially overfling more than we can overscroll, don't
                        // allow the weird behavior where you can scroll to a boundary then
                        // fling further.
                        boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
                        boolean isTopOverstep = dataIndex == 0 && firstChildTop == contentTop - mOverScrollDistance;
                        boolean isBottomOverstep = dataIndex + childCount == mItemCount && lastChildBottom == contentBottom + mOverScrollDistance;
                        if (flingVelocity && !(isTopOverstep ||isBottomOverstep)) {
                            if (mFlingRunnable == null) {
                                mFlingRunnable = new SummerFlingRunnable();
                            }
                            mFlingRunnable.start(-initialVelocity);
                        } else {
                            mTouchMode = TOUCH_MODE_REST;
                            if (mFlingRunnable != null) {
                                mFlingRunnable.endFling();
                            }
                        }
                    }
                } else {
                    mTouchMode = TOUCH_MODE_REST;
                }
                break;

            case TOUCH_MODE_OVER_SCROLL:
                if (mFlingRunnable == null) {
                    mFlingRunnable = new SummerFlingRunnable();
                }
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);

                if (Math.abs(initialVelocity) > mMinimumVelocity) {
                    mFlingRunnable.startOverfling(-initialVelocity);
                } else {
                    mFlingRunnable.startSpringback();
                }

                break;
        }

        setPressed(false);

        // Need to redraw since we probably aren't drawing the selector anymore
        invalidate();
        recycleVelocityTracker();

        mActivePointerId = INVALID_POINTER;

    }

    /**
     * 当Touch取消时（某种情况下丢失了touch）
     */
    private void onTouchCancel() {
        switch (mTouchMode) {
            case TOUCH_MODE_OVER_SCROLL:
                if (mFlingRunnable == null) {
                    mFlingRunnable = new SummerFlingRunnable();
                }
                mFlingRunnable.startSpringback();
                break;

            case TOUCH_MODE_OVER_FLING:
                // Do nothing - let it play out.
                break;

            default:
                mTouchMode = TOUCH_MODE_REST;
                setPressed(false);
                recycleVelocityTracker();
        }
        mActivePointerId = INVALID_POINTER;
    }

    /**
     * 跟踪响应滚动事件，并检查处理是否调用滚动或者填充替换子View循环利用
     * @param incrementalDeltaY 增量deltaY
     * @return 是否触碰边缘，目前未实现失踪判定为为实现，需要实现可结合子空间上下边几当前数据位置进行判定
     */
    private boolean trackMotionScroll(int incrementalDeltaY) {
        final int childCount = getChildCount();
        if (childCount == 0) {
            return true;
        }

        final int height = getHeight() - getPaddingBottom() - getPaddingTop();

        if (incrementalDeltaY < 0) {
            incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
        } else {
            incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
        }

        final boolean down = incrementalDeltaY < 0;

        offsetChildrenTopAndBottom(incrementalDeltaY);

        looperBin.fillGap(down);

        return false;
    }

    private boolean isWaitUnLock = false;

    /**
     * <pre>
     * 对于AbsListView的滑动实现最终通过调用trackMotionScroll()方法完成，其做了2件事：
     * 1.调用了RecycleBin类的addScrapView()方法,实现子控件回收复用
     * 2.调用了ViewGroup类中offsetChildrenTopAndBottom()方法：
     *    1)遍历并对子控件的TOP及Bottom进行调整以达到滑动目的;
     *    2)对于超出范围部分在draw函数中通过canvas.clipRect()截取出显示部分。
     *
     * 可参考ViewGroup类中得offsetChildrenTopAndBottom()方法：
     * 1.offsetChildrenTopAndBottom属于一个hide方法，不能在库外用但可以使用子类覆盖，并不建议这样继承修改该方法hide可能会被废弃而导致系统BUG。
     *
     * 对于View滑动实现分为两大体系：普通的独立View和继承自ViewGroup的容器类view。
     * 普通的独立View:
     *      内部没有控件管理，只需绘制自身即可，所以通过了scrollX、Y的修改来实现滑动,修改的是View的绘制偏移,View本身真实位置没有改变;
     * 容器类的View:
     *      内部有管理控件，无法单纯通过scrollX、Y的修改来实现滑动，会影响子控件的事件响应判定等许多问题，需要用一种真实移动其位置的方法最佳;
     *      通过scrollXY修改的是View绘制偏移，而TOP和Bottom的修改调整的是View的真实位置。这样就可以解决子控件对事件响应区域判断及其他问题。
     *      所以android在View中提供了offsetTopAndBottom()方法来修改其真实位置,而例如ListView及ViewGroup继承类的滑动都是遍历了子控件的这个方法。
     *      对所有子控件调整它的TOP和Bottom，下面的offsetChildrenTopAndBottom()方法对ViewGroup中得做了稍微修改，具体源码可参考ViewGroup。
     * </pre>
     *
     * @param offset 滑动偏移距离:手指向上滑动为负数，手指向下滑动是为正数;
     * */
    protected void offsetChildrenTopAndBottom(int offset) {

        /**
         * 无限滑动实现：(两大问题解决：1.大量图片加载需要处理OOM。2.两组滑动事件无缝响应)
         * 1.ComicReaderView使用了图片缓存应对大量图片加载的OOM问题,缓存使用了自动回收机制回收未显示废弃图片,这就必须自己管理滑动事件。
         * 2.ComicReaderListView内嵌带有ComicReaderView得layout布局,由ComicReaderListView全权接管滑动事件,调度滑动事件的传递：
         *  1)ComicReaderView设定为刚好铺满屏幕大小的View。
         *  2)ComicReaderView没有停靠时由ComicReaderListView响应滑动,停靠时由ComicReaderView响应滑动.(注:ComicReaderView完全重合并铺满屏幕为停靠状态)
         *  3)ComicReaderView停靠时向上滑动内部图片未到底部则ComicReaderView处理滑动,到达底部由ComicReaderListView处理,反之向下滑动时未到顶部亦然。
         *  4)ComicReaderView未在屏幕内出现，则ComicReaderListView响应滑动.pickVisibleComicReaderView()未选取到控件的情况；
         *  5)事件响应每次滑动所偏移距离offset得值并非最小单位值(1);有可能造成滑动停靠判定不准确得问题：通过修正offset值,在offset>dockedDistance时修正滑动距离为停靠距离使其重合
         *  6)ComicReaderView设定铺满全屏的，如果出现2个ComicReaderView存在于同一个屏幕内时,则一定不会出现停靠状态参考2)号规则。
         *  7)pickVisibleComicReaderView(),漫画阅读器选取时如果存在屏幕内出现2个的情况，则更具方向选择最靠近频幕停靠点的那个;
         * */

        boolean isUpwardSliding = offset < 0;
        ComicReaderView comicReaderView = pickVisibleComicReaderView(isUpwardSliding);
        int dockedDistance = calcDockedDistance(comicReaderView);
        if(!isDocked(dockedDistance)) {
            offset = correctingDockedDistance(offset, dockedDistance);
        } else {
            if (dispatchChildrenScroll(offset, comicReaderView)) {
                return;
            }
        }

        ViewWrapper currentView = looperBin.currentView();
        int dataIndex           = currentView.position();
        int visibleViewTop      = currentView.top();
        int visibleViewBottom   = currentView.bottom();

        int unlockDataIndex = 0;
        if(visibleViewTop + offset > getTop()) {
            unlockDataIndex = dataIndex - 1;
            unlockDataIndex = Math.max(unlockDataIndex, 0);
        }
        if(visibleViewBottom + offset < getBottom()) {
            unlockDataIndex = dataIndex + 1;
            unlockDataIndex = Math.min(unlockDataIndex, mItemCount);
        }
        boolean isUnlock = mComicReaderListener.onCheckUnlock(unlockDataIndex, mItemCount);
        if (isUnlock) {
            isWaitUnLock = true;
            return;
        } else if(isWaitUnLock) {
            isWaitUnLock = false;
            looperBin.fillData();
        }

        final boolean cannotScrollDown  = (dataIndex == 0 && (visibleViewTop + offset) >= getTop());
        final boolean cannotScrollUp    = (dataIndex == (mItemCount - 1) && (visibleViewBottom + offset)  <= getBottom());

        if(cannotScrollDown) {
            offset = getTop() - visibleViewTop;
        }
        if(cannotScrollUp) {
            offset = getBottom() - visibleViewBottom;
        }

        if(offset == 0 && (cannotScrollDown || cannotScrollUp)) {
            mComicReaderListener.onSlideToEdge(dataIndex, mItemCount);
            return;
        }

        final int childViewCount = getChildCount();
        for (int i = 0; i < childViewCount; i++) {
            final View childView = getChildAt(i);
            childView.offsetTopAndBottom(offset);
        }

    }

    /**
     * 从ListView的子项里根据滑动方向选取出可见的ComicReaderView,选取滑动方向上最快靠近顶点的哪一个*/
    protected ComicReaderView pickVisibleComicReaderView(boolean isUpwardSliding) {
        ComicReaderView comicReaderView = looperBin.currentView().findComicReaderView();
        if(comicReaderView.getLocalVisibleRect(new Rect())) {
            return comicReaderView;
        }
        return looperBin.nextView().findComicReaderView();
    }

    /**
     * @param offset 滑动偏移距离:手指向上滑动为负数，手指向下滑动是为正数;*/
    private boolean dispatchChildrenScroll(int offset, ComicReaderView comicReaderView) {
        // todo 需要解决解锁时上下状态没有更新导致的跳过章节的情况
        if(comicReaderView == null) {
            return false;
        }
        if(offset > 0 && comicReaderView.isToTheTop()) {
            comicReaderView.scrollBy(0, 0);
            //向下滑动到顶了
            return false;
        }
        if(offset < 0 && comicReaderView.isToTheBottom()) {
            comicReaderView.scrollBy(0, 0);
            //向上滑动到底了
            return false;
        }

        comicReaderView.scrollBy(0, offset);
        return true;
    }

    /**
     * @param dockedDistance 停靠距离
     * @return 是否停靠
     */
    private boolean isDocked(int dockedDistance) {
        return Math.abs(dockedDistance) <= 0;
    }

    /**
     * 判断滑动偏移距离大于停靠剩余距离时修正滑动偏移距离,避免偏移距离过大跳过停靠判定条件的可能*/
    private int correctingDockedDistance(int offset, int dockedDistance) {
        int absOffset = Math.abs(offset);
        int absDockedDistance = Math.abs(dockedDistance);
        //判定方向，由于距离是没有方向的，修正offset需要继承其滑动的方向
        int minus = offset < 0 ? -1 : 1;
        if(absOffset > absDockedDistance) {
            offset = minus * absDockedDistance;
        }
        return offset;
    }

    /**
     * 计算漫画阅读器顶点到ListView顶点之间的距离，携带了方向，负数为高出的距离,正数为低出的距离*/
    private int calcDockedDistance(ComicReaderView comicReaderView) {
        if(comicReaderView == null) {
            //当漫画阅读器未出现在屏幕内的时候，距离理论上来说应该是无穷大得值。
            return Integer.MAX_VALUE;
        }

        int[] outListViewLocation = new int[2];
        getLocationOnScreen(outListViewLocation);
        int[] outReaderViewLocation = new int[2];
        comicReaderView.getLocationOnScreen(outReaderViewLocation);
        int dockedDistance = outReaderViewLocation[1] - outListViewLocation[1];
        return dockedDistance;
    }

    //endregion #滑动 end

    //region    #循环填充核心 begin

    /**
     * 设置适配器，用于获取数据适配，复用控件。<br />
     * 设置适配器时如果存在适配器则会注销旧的适配器观察者监听，绑定新的观察者。并且会重置 {@link LooperBin}
     * @param adapter
     */
    public void setAdapter(BaseAdapter adapter) {
        // 如果存在就注销，重新接受新的。这段如果引起异常也可以移除
        if(mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        mAdapter = adapter;
        mItemCount = mAdapter == null ? 0 : mAdapter.getCount();

        if (mDataSetObserver == null) {
            mDataSetObserver = new AdapterDataSetObserver();
        }
        if (mAdapter != null) {
            looperBin.reset();
            mAdapter.registerDataSetObserver(mDataSetObserver);
        }
    }

    /**
     * 填充数据
     */
    private void fillData() {
        looperBin.fillData();
        invalidate();
        mComicReaderListener.onCheckUnlock(looperBin.currentView().position(), mItemCount);
    }

    class LooperBin {

        private static final int PREVIOUS = 0;
        private static final int CURRENT = 1;
        private static final int NEXT = 2;

        private final ViewWrapper[] viewList = {null, null, null};
        private ViewWrapper mobileView;

        private ViewWrapper previousView() {
            return viewList[PREVIOUS];
        }
        private ViewWrapper currentView() {
            return viewList[CURRENT];
        }
        private ViewWrapper nextView() {
            return viewList[NEXT];
        }

        /**
         * 循环子控件未满
         * @return 如果循环列表中的子控件或自由移动控件其中有一个为 null 则位 true，否则 false
         */
        private boolean isNotFull() {
            if(mobileView == null) {
                return true;
            }
            for(ViewWrapper view : viewList) {
                if(view == null) {
                    return true;
                }
            }
            return false;
        }

        private void reset() {
            for(int index = 0; index < viewList.length; index++) {
                if(viewList[index] != null) {
                    viewList[index].recycle();
                }
                viewList[index] = new ViewWrapper();
            }
            if(mobileView != null) {
                mobileView.recycle();
            }
            mobileView = new ViewWrapper();
        }

        private void fillData() {
            if(isNotFull()) {
                reset();
            }

            for(ViewWrapper viewWrapper : viewList) {
                viewWrapper.fillView();
            }

            previousView().comicReaderViewScrollTo(0, -Integer.MAX_VALUE);
            nextView().comicReaderViewScrollTo(0, 0);
        }


        /**
         * 遍历运行
         * @param function
         * @return
         */
        private boolean forEach(Consumer<ViewWrapper> function) {
            if(isNotFull()) {
                return false;
            }
            for(ViewWrapper viewWrapper : viewList) {
                function.accept(viewWrapper);
            }
            function.accept(mobileView);
            return true;
        }

        private void onLayout(boolean changed, int l, int t, int r, int b) {
            if(isNotFull()) {
                return;
            }

            final int viewListSize = viewList.length;
            final ViewWrapper[] children  = viewList;

            int lastViewBottom = 0;
            if(!initLayout) {
                //以可视View的位置不变作为上下布局调整的标准，这样可以避免因为数据更新导致的布局大小变化，从而移动了可视布局位置的情况
                lastViewBottom = children[1].top() - children[0].getMeasuredHeight();
            }

            for(int index =0; index < viewListSize; index++) {
                final ViewWrapper child = children[index];
                layoutChildren(child.view, lastViewBottom);
                lastViewBottom = child.bottom();
            }
            if(initLayout) {
                correctionViewListDock();
                initLayout = false;
            }
            layoutChildren(mobileView.view, lastViewBottom);
        }

        private void layoutChildren(final View child, final int lastViewBottom) {
            final int w = child.getMeasuredWidth();
            final int h = child.getMeasuredHeight();
            final int childLeft = 0;
            final int childTop = lastViewBottom;
            final int childRight = childLeft + w;
            final int childBottom = childTop + h;
            child.layout(childLeft, childTop, childRight, childBottom);
        }


        private void movePosition(int position) {

            if (isNotFull()) {
                return;
            }

            int previousIndex = position - 1;
            previousIndex = previousIndex >= 0 ? previousIndex : INVALID_POINTER;
            int nextIndex = position+1;
            nextIndex = nextIndex <= mItemCount-1 ? nextIndex : INVALID_POINTER;

            previousView().setPosition(previousIndex);
            currentView().setPosition(position);
            nextView().setPosition(nextIndex);

            correctionViewListDock();
            currentView().comicReaderViewScrollTo(0, 0);

        }

        /**
         * 填充间隙，朝一个方向滚动是，需要使用自由的控件来填补滚动后空余出来的空白间隙位置，并将超出的控件设置为自由控件。
         * @param down 方向 true 向上滑动，填补下部
         */
        private void fillGap(boolean down) {
            if (down) {
                final int currentBottom = currentView().bottom();
                fillDown(currentBottom);
            } else {
                final int currentTop = currentView().top();
                fillUp(currentTop);
            }
        }

        private void fillDown(int currentBottom) {
            int end = getTop();
            ViewWrapper viewWrapper = nextView();
            int index = viewWrapper.position();
            if (currentBottom <= end && index < mItemCount) {
                index = index + 1;
                replaceView(true, viewWrapper.bottom(), index);

                ViewWrapper tmpView = viewList[0];
                viewList[0] = viewList[1];
                viewList[1] = viewList[2];
                viewList[2] = mobileView;
                recyclerView(tmpView);

                if(mComicReaderListener != null) {
                    mComicReaderListener.onReading(currentView().position(), mItemCount);
                }
            }
        }

        private void fillUp(int currentTop) {
            int end = getBottom();
            ViewWrapper viewWrapper = previousView();
            int index = viewWrapper.position();
            if (currentTop >= end && index >= 0) {
                index = index - 1;
                replaceView(false, viewWrapper.top(), index);

                ViewWrapper tmpView = viewList[2];
                viewList[2] = viewList[1];
                viewList[1] = viewList[0];
                viewList[0] = mobileView;
                recyclerView(tmpView);

                if(mComicReaderListener !=null) {
                    mComicReaderListener.onReading(currentView().position(), mItemCount);
                }
            }
        }

        /**
         * 使用自由移动的 mobileView 替换填补上由于上下滑动所缺失出来的位置，如果是下滑则补充到数组首部，如果是下滑，这补充到尾部，
         * 并且将 mobileView 的布局位置移动到衔接处
         * @param down 滑动方向，下滑为 true, 上滑为 false
         * @param y 衔接处的Y轴坐标，
         * @param position
         */
        private void replaceView(boolean down, int y, int position) {

            position = position >= 0 ? position : INVALID_POINTER;
            position = position < mItemCount ? position : INVALID_POINTER;

            final int h = mobileView.getMeasuredHeight();
            final int childTop = down ? y : y - h;
            mobileView.offsetTopAndBottom(childTop - mobileView.top());
            mobileView.comicReaderViewScrollTo(0, down ? 0 : -Integer.MAX_VALUE);
            mobileView.setPosition(position);

            if (position == INVALID_POINTER) {
                return;
            }

            mobileView.fillView();
            if(mComicReaderListener != null) {
                mComicReaderListener.onPreRead(position, mItemCount);
            }
        }

        /**
         * 回收循环列表子项，将超出的View设置到自由移动的项目中，用于下次替换
         * @param viewWrapper 超出列表项待移出的 ViewWrapper
         */
        private void recyclerView(ViewWrapper viewWrapper) {
            viewWrapper.findComicReaderView().stopAllLoad();
            mobileView = viewWrapper;
        }

        /**
         * 修正循环列表整体偏移，使其能够恰巧保证当前可视子项正好完整停靠在屏幕内
         */
        private void correctionViewListDock() {
            int offsetY = 0 - currentView().top();
            for(ViewWrapper viewWrapper : looperBin.viewList) {
                viewWrapper.offsetTopAndBottom(offsetY);
            }
            looperBin.mobileView.offsetTopAndBottom(offsetY);
        }

    }

    /**
     * View包装器，用于对ItemView 细节控制 提供统一门面
     */
    private class ViewWrapper {

        private static final int IndexTag = (4 << 24);

        private final View view;
        private final ComicReaderView comicReaderView;
        private int position = INVALID_POINTER;

        private ViewWrapper() {
            this.view = mAdapter.getView(position, null, LoopComicReaderView.this);
            this.comicReaderView = view.findViewById(R.id.comicReaderView);
            // todo: 进入页面强行使阅读页满屏
//            comicReaderView.getLayoutParams().height = EquipmentInfoUtil.getScreenHeight();
            addViewInLayout(view,  -1, view.getLayoutParams(), true);
        }

        private void recycle() {
            removeView(view);
        }

        /**
         * 根据绑定 position 提取数据来填充新的 View。
         * @return 返回通过新数据填充的 View
         */
        private View fillView() {
            int position = position();
            if(position < 0)
                return view;
            if(position > mItemCount)
                return view;
            if(position == INVALID_POINTER)
                return view;
            return mAdapter.getView(position, view, LoopComicReaderView.this);
        }

        private ComicReaderView findComicReaderView() {
            return comicReaderView;
        }

        /**
         * 滑动循环项目中的阅读器View到某点
         * @param x x坐标
         * @param y y坐标
         */
        private void comicReaderViewScrollTo(int x, int y) {
            comicReaderView.scrollTo(x, y);
        }

        private void setPosition(int position) {
            view.setTag(IndexTag, position);
            this.position = position;
        }

        private int position() {
            Object indexObj = view.getTag(IndexTag);
            return indexObj == null ? INVALID_POINTER : (int)indexObj;
//                return position;
        }

        private void offsetTopAndBottom(int offset) {
            view.offsetTopAndBottom(offset);
        }

        private int getMeasuredHeight() {
            return view.getMeasuredHeight();
        }

        private int top() {
            return view.getTop();
        }

        private int bottom() {
            return view.getBottom();
        }

    }

    //endregion #循环填充核心 end



//    private boolean contentFits() {
//        final int childCount = getChildCount();
//        if (childCount == 0) return true;
//        if (childCount != mItemCount) return false;
//
//        return getChildAt(0).getTop() >= 0 && getChildAt(childCount - 1).getBottom() <= getHeight();
//    }

    public class SummerFlingRunnable implements Runnable {
        /**
         * Tracks the decay of a fling scroll
         */
        private final SummerOverScroller mScroller;

        /**
         * Y value reported by mScroller on the previous fling
         */
        private int mLastFlingY;

        private final Runnable mCheckFlywheel = new Runnable() {
            @Override
            public void run() {
                final int activeId = mActivePointerId;
                final VelocityTracker vt = mVelocityTracker;
                final SummerOverScroller scroller = mScroller;
                if (vt == null || activeId == INVALID_POINTER) {
                    return;
                }

                vt.computeCurrentVelocity(1000, mMaximumVelocity);
                final float yvel = -vt.getYVelocity(activeId);

                if (Math.abs(yvel) >= mMinimumVelocity
                        && scroller.isScrollingInDirection(0, yvel)) {
                    // Keep the fling alive a little longer
                    postDelayed(this, FLYWHEEL_TIMEOUT);
                } else {
                    endFling();
                    mTouchMode = TOUCH_MODE_SCROLL;
                }
            }
        };

        private static final int FLYWHEEL_TIMEOUT = 40;

        SummerFlingRunnable() {
            mScroller = new SummerOverScroller(getContext(),new DecelerateInterpolator(),false);
        }


        void start(int initialVelocity) {
            int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
            mLastFlingY = initialY;
//            mScroller.setInterpolator(null);

            //停止使用SummerOverScroller默认无限叠加加速度，控制加速叠加上限 summer添加
            if(!mScroller.isFinished()){
                float currVelocity = mScroller.getCurrVelocity();
                if (Math.signum(initialVelocity) == Math.signum(currVelocity)) {
                    initialVelocity += currVelocity;
                }
                initialVelocity = Math.min(initialVelocity,mCumulativeMaximumVelocity);
            }
            //结束

            mScroller.fling(0, initialY, 0, initialVelocity,
                    0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
            mTouchMode = TOUCH_MODE_FLING;
            postOnAnimation(this);
        }

        void startSpringback() {
            if (mScroller.springBack(0, getScrollY(), 0, 0, 0, 0)) {
                mTouchMode = TOUCH_MODE_OVER_FLING;
                invalidate();
                postOnAnimation(this);
            } else {
                mTouchMode = TOUCH_MODE_REST;
            }
        }

        void startOverfling(int initialVelocity) {
//            mScroller.setInterpolator(null);
            mScroller.fling(0, getScrollY(), 0, initialVelocity, 0, 0,
                    Integer.MIN_VALUE, Integer.MAX_VALUE, 0, getHeight());
            mTouchMode = TOUCH_MODE_OVER_FLING;
            invalidate();
            postOnAnimation(this);
        }

        void edgeReached(int delta) {
            mScroller.notifyVerticalEdgeReached(getScrollY(), 0, mOverFlingDistance);
            final int overscrollMode = getOverScrollMode();
            if (overscrollMode == OVER_SCROLL_ALWAYS || (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS /*&& !contentFits()*/)) {
                mTouchMode = TOUCH_MODE_OVER_FLING;
            } else {
                mTouchMode = TOUCH_MODE_REST;
            }
            invalidate();
            postOnAnimation(this);
        }

        void endFling() {
            mTouchMode = TOUCH_MODE_REST;
            removeCallbacks(this);
            removeCallbacks(mCheckFlywheel);
            mScroller.abortAnimation();

        }

        void flywheelTouch() {
            postDelayed(mCheckFlywheel, FLYWHEEL_TIMEOUT);
        }


        @Override
        public void run() {
            switch (mTouchMode) {
                default:
                    endFling();
                    return;

                case TOUCH_MODE_SCROLL:
                    if (mScroller.isFinished()) {
                        return;
                    }
                    // Fall through
                case TOUCH_MODE_FLING: {

                    if (mItemCount == 0 || getChildCount() == 0) {
                        endFling();
                        return;
                    }

                    final SummerOverScroller scroller = mScroller;
                    boolean more = scroller.computeScrollOffset();
                    final int y = scroller.getCurrY();

                    int delta = mLastFlingY - y;
                    if (delta > 0) {
                        delta = Math.min(getHeight() - getPaddingBottom() - getPaddingTop() - 1, delta);
                    } else {
                        delta = Math.max(-(getHeight() - getPaddingBottom() - getPaddingTop() - 1), delta);
                    }

                    final boolean atEdge = trackMotionScroll(delta);
                    final boolean atEnd = atEdge && (delta != 0);
                    if (atEnd) {
                        if (more) {
                            edgeReached(delta);
                        }
                        break;
                    }

                    if (more && !atEnd) {
                        if (atEdge) invalidate();
                        mLastFlingY = y;
                        postOnAnimation(this);
                    } else {
                        endFling();
                    }
                    break;
                }

                case TOUCH_MODE_OVER_FLING: {
                    final SummerOverScroller scroller = mScroller;
                    if (scroller.computeScrollOffset()) {
                        final int scrollY = getScrollY();//mScrollY;
                        final int currY = scroller.getCurrY();
                        final int deltaY = currY - scrollY;
                        if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
                                0, mOverFlingDistance, false)) {
                            final boolean crossDown = scrollY <= 0 && currY > 0;
                            final boolean crossUp = scrollY >= 0 && currY < 0;
                            if (crossDown || crossUp) {
                                int velocity = (int) scroller.getCurrVelocity();
                                if (crossUp) velocity = -velocity;

                                // Don't flywheel from this; we're just continuing things.
                                scroller.abortAnimation();
                                start(velocity);
                            } else {
                                startSpringback();
                            }
                        } else {
                            invalidate();
                            postOnAnimation(this);
                        }
                    } else {
                        endFling();
                    }
                    break;
                }
            }
        }
    }

    private class AdapterDataSetObserver extends DataSetObserver {
        @Override
        public void onChanged() {
            mItemCount = mAdapter.getCount();
            fillData();
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            mItemCount = 0;
            requestLayout();
        }
    }

    /**
     * 无限漫画阅读器事件监听
     */
    public interface ComicReaderListener {
        /**
         * 预加载事件触发，当无线阅读器中触发阅读事件时，会提前触发更具方向所在的下一个数据加载节点
         * @param preReadItemIndex 下一个显示数据的索引号
         * @param totalItemCount 数据项总数
         */
        void onPreRead(int preReadItemIndex, int totalItemCount);

        /**
         * 阅读事件触发，当无限漫画阅读器中下一个漫画阅读器第一次进入显示窗口时触发。
         * @param currentVisibleItemIndex 当前显示数据项目索引号
         * @param totalItemCount 数据项目总数
         */
        void onReading(int currentVisibleItemIndex, int totalItemCount);

        /**
         * 检查解锁
         * @param unlockItemIndex   准备校验解锁的章节索引，滑动向上则为当前章节的上一章，向下则为下一章
         * @param totalItemCount    所有章节的总索引数，为数组长度。
         * @return 需要解锁则返回true,否则false
         */
        boolean onCheckUnlock(int unlockItemIndex, int totalItemCount);

        /**
         * 在滑动到底部边缘或顶部边缘时触发，该方法会因为滚动事件被多次触发，目前没有在触发点做单次限制，使用者需要在具体响响应时做处理过滤短时间内多次响应。
         * 如果有需要精确处理只在同一个方向滑动时触发一次，则需要在触发事件点做状态切换来限制单次触发，在朝一个方向上滑动时，第一次触发后不在触发，直到发生反方向滑动且处理脏区域后才切换状态
         * @param currentVisibleItemIndex   当前阅读的章节显示View索引，注意该索引从0开始。
         * @param totalItemCount            所有章节的总索引数，为数组长度。
         */
        void onSlideToEdge(int currentVisibleItemIndex, int totalItemCount);
    }

}
